<!DOCTYPE html>
<meta charset=utf-8>
<meta name="assert"
content="This test checks the output of Cubic Bézier functions" />
<title>Tests for the output of Cubic Bézier timing functions</title>
<link rel="help"
href="https://drafts.csswg.org/css-easing/#cubic-bezier-timing-functions">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="testcommon.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';

// Precision of major rendering engines' layout systems.
const epsilon = 0.02;

function assert_style_left_at(animation, time, easingFunction) {
  animation.currentTime = time;
  var portion = time / animation.effect.getTiming()['duration'];
  assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left),
                       easingFunction(portion) * 100,
                       epsilon,
                       'The left of the animation should be approximately ' +
                       easingFunction(portion) * 100 + ' at ' + time + 'ms');
}

test(function(t) {
  var target = createDiv(t);
  target.style.position = 'absolute';
  var anim = target.animate(
    // http://cubic-bezier.com/#.5,1,.5,0
    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
      { left: '100px' } ],
      { duration: 1000,
        fill: 'forwards',
        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
  var keyframeEasing = function(x) {
    assert_greater_than_equal(x, 0.0,
      'This function should be called in [0, 1.0] range');
    assert_less_than_equal(x, 1.0,
      'This function should be called in [0, 1.0] range');
    return cubicBezier(0.5, 1, 0.5, 0)(x);
  }
  var keyframeEasingExtrapolated = function(x) {
    assert_greater_than(x, 1.0,
      'This function should be called in (1.0, infinity) range');
    // p3x + (p2y - p3y) / (p2x - p3x) * (x - p3x)
    return 1.0 + (0 - 1) / (0.5 - 1) * (x - 1.0);
  }
  var effectEasing = function(x) {
    return cubicBezier(0, 1.5, 1, 1.5)(x);
  }

  // The effect-easing produces values greater than 1 in (0.23368794, 1)
  assert_style_left_at(anim, 0, function(x) {
    return keyframeEasing(effectEasing(x));
  });
  assert_style_left_at(anim, 230, function(x) {
    return keyframeEasing(effectEasing(x));
  });
  assert_style_left_at(anim, 240, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  // Near the extreme point of the effect-easing function
  assert_style_left_at(anim, 700, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  assert_style_left_at(anim, 990, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  assert_style_left_at(anim, 1000, function(x) {
    return keyframeEasing(effectEasing(x));
  });
}, 'cubic-bezier easing with input progress greater than 1');

test(function(t) {
  var target = createDiv(t);
  target.style.position = 'absolute';
  var anim = target.animate(
    // http://cubic-bezier.com/#0,1.5,1,1.5
    [ { left: '0px', easing: 'cubic-bezier(0, 1.5, 1, 1.5)' },
      { left: '100px' } ],
      { duration: 1000,
        fill: 'forwards',
        easing: 'cubic-bezier(0, 1.5, 1, 1.5)' });
  var easing = function(x) {
    assert_greater_than_equal(x, 0.0,
      'This function should be called in [0, 1.0] range');
    assert_less_than_equal(x, 1.0,
      'This function should be called in [0, 1.0] range');
    return cubicBezier(0, 1.5, 1, 1.5)(x);
  }
  var easingExtrapolated = function(x) {
    assert_greater_than(x, 1.0,
      'This function should be called in negative range');
    // For cubic-bezier(0, 1.5, 1, 1.5), the tangent at the
    // endpoint (x = 1.0) is infinity so we should just return 1.0.
    return 1.0;
  }

  // The effect-easing produces values greater than 1 in (0.23368794, 1)
  assert_style_left_at(anim, 0, function(x) {
    return easing(easing(x))
  });
  assert_style_left_at(anim, 230, function(x) {
    return easing(easing(x))
  });
  assert_style_left_at(anim, 240, function(x) {
    return easingExtrapolated(easing(x));
  });
  // Near the extreme point of the effect-easing function
  assert_style_left_at(anim, 700, function(x) {
    return easingExtrapolated(easing(x));
  });
  assert_style_left_at(anim, 990, function(x) {
    return easingExtrapolated(easing(x));
  });
  assert_style_left_at(anim, 1000, function(x) {
    return easing(easing(x))
  });
}, 'cubic-bezier easing with input progress greater than 1 and where the ' +
   'tangent on the upper boundary is infinity');

test(function(t) {
  var target = createDiv(t);
  target.style.position = 'absolute';
  var anim = target.animate(
    // http://cubic-bezier.com/#.5,1,.5,0
    [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' },
      { left: '100px' } ],
      { duration: 1000,
        fill: 'forwards',
        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
  var keyframeEasing = function(x) {
    assert_greater_than_equal(x, 0.0,
      'This function should be called in [0, 1.0] range');
    assert_less_than_equal(x, 1.0,
      'This function should be called in [0, 1.0] range');
    return cubicBezier(0.5, 1, 0.5, 0)(x);
  }
  var keyframeEasingExtrapolated = function(x) {
    assert_less_than(x, 0.0,
      'This function should be called in negative range');
    // p0x + (p1y - p0y) / (p1x - p0x) * (x - p0x)
    return (1 / 0.5) * x;
  }
  var effectEasing = function(x) {
    return cubicBezier(0, -0.5, 1, -0.5)(x);
  }

  // The effect-easing produces negative values in (0, 0.766312060)
  assert_style_left_at(anim, 0, function(x) {
    return keyframeEasing(effectEasing(x));
  });
  assert_style_left_at(anim, 10, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  // Near the extreme point of the effect-easing function
  assert_style_left_at(anim, 300, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  assert_style_left_at(anim, 750, function(x) {
    return keyframeEasingExtrapolated(effectEasing(x));
  });
  assert_style_left_at(anim, 770, function(x) {
    return keyframeEasing(effectEasing(x));
  });
  assert_style_left_at(anim, 1000, function(x) {
    return keyframeEasing(effectEasing(x));
  });
}, 'cubic-bezier easing with input progress less than 0');

test(function(t) {
  var target = createDiv(t);
  target.style.position = 'absolute';
  var anim = target.animate(
    // http://cubic-bezier.com/#0,-0.5,1,-0.5
    [ { left: '0px', easing: 'cubic-bezier(0, -0.5, 1, -0.5)' },
      { left: '100px' } ],
      { duration: 1000,
        fill: 'forwards',
        easing: 'cubic-bezier(0, -0.5, 1, -0.5)' });
  var easing = function(x) {
    assert_greater_than_equal(x, 0.0,
      'This function should be called in [0, 1.0] range');
    assert_less_than_equal(x, 1.0,
      'This function should be called in [0, 1.0] range');
    return cubicBezier(0, -0.5, 1, -0.5)(x);
  }
  var easingExtrapolated = function(x) {
    assert_less_than(x, 0.0,
      'This function should be called in negative range');
    // For cubic-bezier(0, -0.5, 1, -0.5), the tangent at the
    // endpoint (x = 0.0) is infinity so we should just return 0.0.
    return 0.0;
  }

  // The effect-easing produces negative values in (0, 0.766312060)
  assert_style_left_at(anim, 0, function(x) {
    return easing(easing(x))
  });
  assert_style_left_at(anim, 10, function(x) {
    return easingExtrapolated(easing(x));
  });
  // Near the extreme point of the effect-easing function
  assert_style_left_at(anim, 300, function(x) {
    return easingExtrapolated(easing(x));
  });
  assert_style_left_at(anim, 750, function(x) {
    return easingExtrapolated(easing(x));
  });
  assert_style_left_at(anim, 770, function(x) {
    return easing(easing(x))
  });
  assert_style_left_at(anim, 1000, function(x) {
    return easing(easing(x))
  });
}, 'cubic-bezier easing with input progress less than 0 and where the ' +
   'tangent on the lower boundary is infinity');

</script>
</body>
